package li.kaeppe.travel.tracker.web.component;

import java.io.IOException;

import javax.el.ELException;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;

import li.kaeppe.travel.tracker.domain.User;

import org.springframework.security.GrantedAuthority;
import org.springframework.security.context.SecurityContextHolder;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.FaceletException;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagException;
import com.sun.facelets.tag.TagHandler;

public class AuthorizedHandler extends TagHandler {

	/* **************************************************** */
	/*                      CONSTANTS                       */
	/* **************************************************** */

	public static final String ATTR_ALL_GRANTED = "ifAllGranted";
	
	public static final String ATTR_ANY_GRANTED = "ifAnyGranted"; 
	
	public static final String ATTR_NOT_GRANTED = "ifNotGranted";
	
	public static final String SEPARATOR = ",";
	
	/* **************************************************** */
	/*                     MEMBER FIELDS                    */
	/* **************************************************** */

	private TagAttribute allGrantedRoles = null;
	
	private TagAttribute anyGrantedRoles = null;
	
	private TagAttribute notGrantedRoles = null;
	
	
	/* **************************************************** */
	/*                      CONSTRUCTOR                     */
	/* **************************************************** */

	public AuthorizedHandler(TagConfig config) {
		super(config);
		
		allGrantedRoles = this.getAttribute(ATTR_ALL_GRANTED);
		anyGrantedRoles = this.getAttribute(ATTR_ANY_GRANTED);
		notGrantedRoles = this.getAttribute(ATTR_NOT_GRANTED);
		
		// only one attribute can be set
		if( (allGrantedRoles != null && anyGrantedRoles != null) ||
			(anyGrantedRoles != null && notGrantedRoles != null) ||
			(allGrantedRoles != null && notGrantedRoles != null) ||
			(allGrantedRoles == null && anyGrantedRoles == null && notGrantedRoles == null) ) {
			throw new TagException(config.getTag(), "More than one attribute set");
		}
	}
	
	/* **************************************************** */
	/*                    PUBLIC METHODS                    */
	/* **************************************************** */

	public void apply(FaceletContext context, UIComponent component)
			throws IOException, FacesException, FaceletException, ELException {
		boolean authorized = false;
		User user = getUser();
		
		if( (user != null) && 
			(user.getAuthorities() != null) &&
			(user.getAuthorities().length > 0)) {
			GrantedAuthority[] authorities = user.getAuthorities();
			
			authorized = isAllGrantedRoles(authorities, allGrantedRoles) &&
						 isAnyGrantedRoles(authorities, anyGrantedRoles) &&
						 isNotGrantedRoles(authorities, notGrantedRoles);
		}
		
		if(authorized == true) {
			this.nextHandler.apply(context, component);
		}
	}
	
	/* **************************************************** */
	/*                  PROTECTED METHODS                   */
	/* **************************************************** */

	protected boolean isAllGrantedRoles(GrantedAuthority[] authorities, TagAttribute requiredRolesAttr) {
		boolean authorized = true;
		
		if(requiredRolesAttr != null) {
			String[] requiredRoles = getRoles(requiredRolesAttr);
			
			for(String role: requiredRoles) {
				boolean hasCurrentRole = false;
				
				innerLoop: for(GrantedAuthority authority: authorities) {
					if(authority.getAuthority().equals(role)) {
						hasCurrentRole = true;
						break innerLoop;
					}
				}
				
				authorized = authorized && hasCurrentRole;
			}
		} else {
			authorized = true;
		}
		
		return authorized;
	}

	protected boolean isAnyGrantedRoles(GrantedAuthority[] authorities, TagAttribute requiredRolesAttr) {
		boolean authorized = false;
		
		if(requiredRolesAttr != null) {
			String[] requiredRoles = getRoles(requiredRolesAttr);
			
			outterLoop: for(String role: requiredRoles) {
				for(GrantedAuthority authority: authorities) {
					if(authority.getAuthority().equals(role)) {
						authorized = true;
						break outterLoop;
					}
				}
			}
		} else {
			authorized = true;
		}
		
		return authorized;
	}
	
	protected boolean isNotGrantedRoles(GrantedAuthority[] authorities, TagAttribute requiredRolesAttr) {
		boolean authorized = true;
		
		if(requiredRolesAttr != null) {
			String[] requiredRoles = getRoles(requiredRolesAttr);
			
			for(String role: requiredRoles) {
				boolean hasCurrentRole = false;
				
				innerLoop: for(GrantedAuthority authority: authorities) {
					if(authority.getAuthority().equals(role)) {
						hasCurrentRole = true;
						break innerLoop;
					}
				}
				
				authorized = authorized && !hasCurrentRole;
			}
		} else {
			authorized = true;
		}
		
		return authorized;
	}
	
	/* **************************************************** */
	/*                   PRIVATE METHODS                    */
	/* **************************************************** */

	private User getUser() {
		Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		return (User)obj;
	}
	
	private String[] getRoles(TagAttribute attribute) {
		String rolesStr = attribute.getValue();
		
		String[] roles = rolesStr.split(SEPARATOR);
		
		for(String role: roles) {
			role = role.trim();
		}
		
		return roles;
	}
	
	/* **************************************************** */
	/*                  GETTERS AND SETTERS                 */
	/* **************************************************** */
	
}
